package ru.qrck.quitetaskmanager;


import java.util.ArrayList;

import android.os.Bundle;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import ru.qrck.quitetaskmanager.ForegroundService;


public class TaskManager extends ListActivity {
	private static final String TAG = "QTM";
	
	private static final int MENU_REFRESH = 0;
	private static final int MENU_SETTINGS = 1;
	private static final int MENU_ABOUT = 2;
	private static final int MENU_OTHER = 3;
	
	private static final int REQUEST_CODE_PREFERENCES = 1;
	
	//private ProcessStatusInfo mPSInfo = new ProcessStatusInfo();
	private PackageManager mPackageManager = null;
	private ActivityManager mActivityManager = null;
	
	
	private int mSelectPosition = 0;
	private int mKillProcessId = 0;
	
	// UI Stub
	// List of the strings to display
	private ArrayList<String> mListView = null;
	// Helper list to quickly lookup pid
	private ArrayList<Integer> mIndexView = null;
	// booleans - collapsed / not collapsed
	private ArrayList<Boolean> mBoolView = null;
	// Ctx
	private Context mContext = this;
	// Update dialog
	private ProgressDialog mDialog = null;	
	// Update Task
	private RefreshTask mRefreshTask;
	
	private ProcessInfoTable mProcTable = new ProcessInfoTable();
	private ProcessUtils mProcUtils = null;
		
	private class ProcessListAdapter extends BaseAdapter {
        public ProcessListAdapter(Context context)
        {
            mContext = context;
        }
       
        /**
         * The number of items in the list is determined by the number of speeches
         * in our array.
         * 
         * @see android.widget.ListAdapter#getCount()
         */
        public int getCount() {
            return mListView.size(); 
        }

        /**
         * Since the data comes from an array, just returning
         * the index is sufficent to get at the data. If we
         * were using a more complex data structure, we
         * would return whatever object represents one 
         * row in the list.
         * 
         * @see android.widget.ListAdapter#getItem(int)
         */
        public Object getItem(int position) {
            return position;
        }

        /**
         * Use the array index as a unique id.
         * @see android.widget.ListAdapter#getItemId(int)
         */
        public long getItemId(int position) {
            return position;
        }

        /**
         * Make a SpeechView to hold each row.
         * @see android.widget.ListAdapter#getView(int, android.view.View, android.view.ViewGroup)
         */
        public View getView(int position, View convertView, ViewGroup parent) {
            ProcessInfoView piv;
            if (convertView == null) {
            	piv = new ProcessInfoView(mContext, mListView.get(position), mIndexView.get(position), mBoolView.get(position));
            } else {
            	piv = (ProcessInfoView)convertView;
            	piv.setTitle(mListView.get(position));
                piv.setProcess(mIndexView.get(position));
                piv.setExpanded(mBoolView.get(position));
            }
            
            return piv;
        }

        public void toggle(int position) {
        	mBoolView.set(position, !mBoolView.get(position));
            notifyDataSetChanged();
        }
        
        /**
         * Remember our context so we can use it when constructing views.
         */
        private Context mContext;
        
    }
    
    /**
     * We will use a SpeechView to display each speech. It's just a LinearLayout
     * with two text fields.
     *
     */
    private class ProcessInfoView extends LinearLayout {
        public ProcessInfoView(Context context, String title, Integer pid, boolean expanded) {
            super(context);
            
            this.setOrientation(VERTICAL);
            
            mPid = pid.intValue();
            mExpanded = expanded;
            
            // Here we build the child views in code. They could also have
            // been specified in an XML file.
            
            setPadding(10, 10, 10, 10);
            
            mTitle = new TextView(context);
            
            mTitle.setTextSize((float) 16.0);
            
            mTitle.setText(title);
            addView(mTitle, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
            
            mDialogue = new TextView(context);
            if (mExpanded)
            	mDialogue.setText(mProcUtils.getProcessInfoString(mPid, false));
            else
            	mDialogue.setText("");

            addView(mDialogue, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); //WRAP_CONTENT
            
            mDialogue.setVisibility(expanded ? VISIBLE : GONE);
        }
        
        /**
         * Convenience method to set the title of a SpeechView
         */
        public void setTitle(String title) {
            mTitle.setText(title);
        }
        
        /**
         * Convenience method to set the dialogue of a SpeechView
         */
        public void setProcess(Integer pid) {
        	mPid = pid.intValue();
        	if (mExpanded)
        		mDialogue.setText(mProcUtils.getProcessInfoString(mPid, false));
        }
        
        /**
         * Convenience method to expand or hide the dialogue
         */
        public void setExpanded(boolean expanded) {
        	mExpanded = expanded;
        	if (mExpanded)
        		mDialogue.setText(mProcUtils.getProcessInfoString(mPid, false));
        	
            mDialogue.setVisibility(expanded ? VISIBLE : GONE);
        }
        
        private TextView mTitle;
        private TextView mDialogue;
        private int mPid;
        private boolean mExpanded = false;
    }
	
	
    // Create running process list
    public void loadProcessList() {
    	Log.v(TAG, "drawProcessList");

   	
    	// List of the running processes
    	ArrayList<RunningAppProcessInfo> processList = (ArrayList<RunningAppProcessInfo>)mActivityManager.getRunningAppProcesses();
    	
    	// List of the running services
    	ArrayList<RunningServiceInfo> serviceList = (ArrayList<RunningServiceInfo>)mActivityManager.getRunningServices(256);
        
        int numProc = processList.size() + serviceList.size();
        
        mListView = new ArrayList<String>(numProc);
        mIndexView = new ArrayList<Integer>(numProc);
        mBoolView = new ArrayList<Boolean>(numProc);

        mProcTable.clearProcessMap();
        
        for (RunningAppProcessInfo info : processList) {
        	int pid = info.pid;
        	String strName = info.processName;
        	
        	mProcTable.setProcessPkgName(pid, strName);
        	
	    	try {
	    		ApplicationInfo appInfo = mPackageManager.getApplicationInfo(strName, PackageManager.GET_META_DATA);
	    		strName = mPackageManager.getApplicationLabel(appInfo).toString();// + "\n ID: " + strName + "";
	    	} catch(NameNotFoundException e) {
	    		//
	    	}
	    	
	    	mProcTable.setProcessName(pid, strName);

        	mIndexView.add(pid);
        	mListView.add(strName);
        	mBoolView.add(false);
        }

        
        for (RunningServiceInfo info : serviceList) {
        	int pid = info.pid;
        	String strName = info.process;
        	
        	String svcName = info.service.getClassName();
        	
        	try  {
        		int len = strName.length();

        		if (len < svcName.length()) {
        			String strSub = svcName.substring(0, len);
        			
            		if (strSub.compareTo(strName) == 0)
            		{
            			svcName = svcName.substring(len);
            		}        		
        		}
        	}
			catch (Exception ex) {
			}

			if (pid == 0) { // this sometimes happens, try to recover the proc name and pid
				String tmpName = strName;
				try {
		    		ApplicationInfo appInfo = mPackageManager.getApplicationInfo(tmpName, PackageManager.GET_META_DATA);
		    		tmpName = mPackageManager.getApplicationLabel(appInfo).toString();
		    	} catch(NameNotFoundException e) {
		    		//
		    	}
				
				int i = mListView.indexOf(tmpName);
				if (i >= 0) {
					int tmpPid = mIndexView.get(i).intValue();
					if (mProcTable.getProcessPkgName(tmpPid).equals(strName)) 
						pid = tmpPid;		
				}
			}
			
			mProcTable.addProcessService(pid, svcName);
        	
        	int idx = mIndexView.indexOf(new Integer(pid));
        	
        	if (idx >= 0) {        		
        		//mListView.set(idx, mListView.get(idx) + "\n S: " + svcName + "");        	
        	}
        	else {
            	mIndexView.add(pid);
            	mListView.add("[SVC] " + strName);// + "\n S: " + svcName + "" );
            	mBoolView.add(false);            	
            	
            	if (mProcTable.getProcessName(pid) == null)
            		mProcTable.setProcessName(pid, "[SVC] " + strName);
            	if (mProcTable.getProcessPkgName(pid) == null)
            		mProcTable.setProcessPkgName(pid, strName);
        	}
        }
    }
    
    private void dismissDialog() {
		if(mDialog != null && mDialog.isShowing()) {
			mDialog.dismiss();
			mDialog = null;
		}
    }
    
    /*
     * The background task class that runs the process lists refresh
     */
	public class RefreshTask extends AsyncTask<Void, Void, Void> {
		private static final String TAG = "RefreshTask";
			
		@Override
		protected Void doInBackground(Void... params) {
			Log.v(TAG, "doInBackground");
			loadProcessList();
			return null;
		}
		@Override
		protected void onPreExecute() {
			Log.v(TAG, "onPreExecute");
			mDialog = new ProgressDialog(mContext);
			mDialog.setTitle("");
			mDialog.setMessage("Loading. Please wait...");
			mDialog.setIndeterminate(true);
			mDialog.show();
		}
		@Override
		protected void onPostExecute(Void result) {
			Log.v(TAG, "onPostExecute");
			dismissDialog();
			//setListAdapter( new ArrayAdapter<String>(mContext, R.layout.list_item, mListView));
			setListAdapter( new ProcessListAdapter(mContext));
			setSelection(mSelectPosition);
			
	    	ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
	    	mActivityManager.getMemoryInfo(memoryInfo);

	    	TaskManager.this.setTitle("QTM: "+ 
	    			"" + mProcUtils.getSysMemTotal() / 1024 + "M " + 
	    			"/ " + 
	    			memoryInfo.availMem/1024/1024 + "M " + 
	    			(memoryInfo.lowMemory ? "[LOW!!]" : "") 
	    			);

		}
		@Override
		protected void onCancelled() {
			Log.v(TAG, "onCancelled");
			dismissDialog();
		}
		
	}

    private void refreshProcessList() {
    	mRefreshTask = new RefreshTask();
    	mRefreshTask.execute();
    }
    
    
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    	Log.v(TAG, "onCreate");
    	
        mPackageManager = this.getPackageManager();
        mActivityManager = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
        
        mProcUtils = new ProcessUtils(mPackageManager, mActivityManager, mProcTable);
        
        ListView listView = getListView();
        listView.setTextFilterEnabled(true);

        listView.setOnItemClickListener(new OnItemClickListener() {
        	
        	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        		((ProcessListAdapter)getListAdapter()).toggle(position);
        	
        	}
		});

        listView.setOnItemLongClickListener(new OnItemLongClickListener() {
        	public boolean onItemLongClick(AdapterView<?> parent, View view,
        			int position, long id) {
        		buildAlertMessageKillProcess(position);
        		return true;
        	}
        });
        
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);

    	Intent intent = new Intent(pref.getBoolean("invert_color", false) 
									? ForegroundService.ACTION_FOREGROUND_IV 
									: ForegroundService.ACTION_FOREGROUND );
        intent.setClass(TaskManager.this, ForegroundService.class);
        startService(intent);
    }
    
    @Override
    protected void onResume() {
    	super.onResume();
    	Log.v(TAG, "onResume");
    	refreshProcessList();
    }

    @Override
    protected void onDestroy() {
    	super.onDestroy();
    	Log.v(TAG, "onDestroy");
    }
    
    @Override
    protected void onNewIntent (Intent intent) {
    	super.onNewIntent(intent);
    	Log.v(TAG, "onNewIntent");
    	//refreshProcessList();
    }
    
    @Override
    protected void onPause() {
    	Log.v(TAG, "onPause");
    	dismissDialog();
    	if(mRefreshTask != null && !mRefreshTask.isCancelled()) {
    		mRefreshTask.cancel(true);
    	}
    	super.onPause();
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	super.onCreateOptionsMenu(menu);
    	menu.add(0, MENU_REFRESH, 0, "Refresh");
    	menu.add(0, MENU_SETTINGS, 0, "Settings");
    	menu.add(0, MENU_ABOUT, 0, "About");
    	//menu.add(0, MENU_SETTINGS, 0, "Settings");
    	return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        
        // The preferences returned if the request code is what we had given
        // earlier in startSubActivity
        if (requestCode == REQUEST_CODE_PREFERENCES) {
            // Read a sample value they have set
            //updateCounterText();
            SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);

        	Intent intent = new Intent(pref.getBoolean("invert_color", false) 
    									? ForegroundService.ACTION_FOREGROUND_IV 
    									: ForegroundService.ACTION_FOREGROUND );
            intent.setClass(TaskManager.this, ForegroundService.class);
            startService(intent);
        }
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch(item.getItemId()) {
    	case MENU_REFRESH:
    		mSelectPosition = 0;
    		refreshProcessList();
    		break;
    	case MENU_SETTINGS:
            Intent launchPreferencesIntent = new Intent().setClass(this, PrefsPage.class);
            
            // Make it a subactivity so we know when it returns
            startActivityForResult(launchPreferencesIntent, REQUEST_CODE_PREFERENCES);
    		break;
    	case MENU_ABOUT:
       		Toast.makeText(getApplicationContext(),
    				"Quite Task Manager, v1.2, alpha of course",
    				Toast.LENGTH_LONG).show();
    		break;
    	case MENU_OTHER:
    		break;
    	}
    	
    	return true;
    }

    private void buildAlertMessageKillProcess(int index) {
        
        mSelectPosition = index;
        int pid = mIndexView.get(index).intValue();
        String processName = mProcTable.getProcessName(pid); 
        
        if(processName == null) {
        	processName = mProcTable.getProcessPkgName(pid);
        }
        if(processName != null) {
        	mKillProcessId = pid;
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
            String label = mProcTable.getProcessPkgName(pid);
            String message = "Are you sure you want to kill ";
            if(label.equals(processName)) {
            	message += processName + "?";
            } else {
            	message += processName + " [" + label + "]?";
            }

	        builder.setMessage(message)
	               .setCancelable(false)
	               .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
	                   public void onClick(final DialogInterface dialog, final int id) {
	                	   boolean bRet = mProcUtils.killProcess(mKillProcessId);
	                	   mKillProcessId = 0;
	                	   if(bRet) {
	                		   refreshProcessList();
	                	   }
	                   }
	               })
	               .setNegativeButton("No", new DialogInterface.OnClickListener() {
	                   public void onClick(final DialogInterface dialog, final int id) {
	                        dialog.cancel();
	                   }
	               });

	        final AlertDialog alert = builder.create();
	        alert.show();
        } else {
    		Toast.makeText(getApplicationContext(),
    				"You cannot kill this process.",
    				Toast.LENGTH_SHORT).show();
        }
    }
}